﻿using System;

namespace GLib
{
    public class STDPipe
    {
        public const int INIT_INT_VALUE = -2147483647;

        public const int SUCCESS = 0;

        public const int FAIL = -1;

        public const int OUT_OF_MEM = -2;

        public const int INPUT_PARA_ERROR = -3;

        public const int OUT_OF_RANGE = -4;

        public const int MIN_PIPE_LENGTH = (1024 * 4096); //package length is in 512 bytes,max received package is 512

        public byte[] m_pMemPool;
        uint m_pHeader;
        uint m_pRear;
        uint m_memPoolSize;

        public STDPipe(uint pipeSize,ref int result)
        {
            if (MIN_PIPE_LENGTH > pipeSize)
            {
                pipeSize = MIN_PIPE_LENGTH;
            }
            result = SUCCESS;
            m_pMemPool = new byte[pipeSize];
            if (null == m_pMemPool)
            {
                result = OUT_OF_MEM;
                return;
            }
            m_pRear = 0;
            m_pHeader = 0;
            m_memPoolSize = pipeSize;
            result = SUCCESS;
        }
        public STDPipe()
        {
            m_pMemPool = null;
            m_pHeader = 0;
            m_pRear = 0;
            m_memPoolSize = 0;
        }
        public int Init(uint pipeSize)
        {

            if (MIN_PIPE_LENGTH > pipeSize)
            {
                pipeSize = MIN_PIPE_LENGTH;
            }
            m_pMemPool = new byte[pipeSize];
            if (null == m_pMemPool)
            {
                return OUT_OF_MEM;
            }
            m_pRear = 0;
            m_pHeader = 0;
            m_memPoolSize = pipeSize;

            return SUCCESS;
        }
        public void Dispose()
        {
            if ( m_pMemPool != null )
            {
                m_pMemPool = null;
            }
        }

        public bool WriteBuffer(uint len ,byte[] msg,uint offset = 0)
        {
            try
            {
                Buffer.BlockCopy(msg, (int)offset, m_pMemPool, (int)m_pHeader, (int)len);
            }
            catch(Exception e)
            {
                return false;
            }
            return true;
        }

        public bool ReadBuffer(uint len, byte[] msg,uint offset = 0)
        {
            try
            {
                Buffer.BlockCopy(m_pMemPool, (int)m_pRear, msg, (int)offset, (int)len);
            }
            catch (Exception e)
            {
                return false;
            }
            return true;
        }

        public uint GetData(ref uint len)
        {
            if (GetDataLen() < len)
            {
                return 0;
            }
            if (m_pRear <= m_pHeader)
            {
                len = m_pHeader - m_pRear;
            }
            else
            {
                len = m_memPoolSize - m_pRear;
            }

            return m_pRear;
        }

        public int ReturnMsgBuff(uint pMsg, uint len)
        {
            uint dataLen = GetDataLen();
            if (len > dataLen)
            {
                return INPUT_PARA_ERROR;
            }
            if (m_pRear != pMsg)
            {
                return SUCCESS;
            }
            m_pRear += len;
            if (m_pRear > m_memPoolSize)
            {
                m_pRear = m_memPoolSize - m_pRear;
                //cocos2d::log("Error in STDPipe::PrepareMsg, wrong format!");
                return INPUT_PARA_ERROR;
            }
            if (m_pRear == m_memPoolSize)
            {
                m_pRear = 0;
            }
            return SUCCESS;
        }

        public int PrepareMsg(ref uint len)
        {
            if (len > GetFreeLen())
            {
                len = 0;
                //cout<<"Error in STDPipe::PrepareMsg, out of memory!"<<endl;
                return -1;
            }
            if (m_pRear > m_pHeader)
            {
                len = (m_pRear - m_pHeader);
            }
            else
            {
                len = (m_memPoolSize - m_pHeader);
            }
            return (int)m_pHeader;
        }

        public int ConfirmAddMsg(uint pMem, uint len)
        {
            if (pMem != m_pHeader)
            {
                //cocos2d::log("Error in STDPipe::ConfirmAddMsg, wrong pMem!");
                return FAIL;
            }

            m_pHeader += len;
            if (m_pHeader > m_memPoolSize)
            {
                //some thing wrong!
                //cocos2d::log("something wrong in ConfirmAddMsg");
                m_pHeader = 0;

                return FAIL;
            }

            if (m_pHeader == m_memPoolSize)
            {
                m_pHeader = 0;
            }
            return SUCCESS;
        }

        public bool Peek(byte[] pBuffer, uint len)
        {
            uint result = GetDataLen();
            if (result < len)
            {
                return false;
            }
            uint buffLen = 0;
            if (m_pHeader >= m_pRear)
            {
                buffLen = m_pHeader - m_pRear;
                if (len <= buffLen)
                {
                    Buffer.BlockCopy(m_pMemPool, (int)m_pRear, pBuffer, 0, (int)len);
                }
                else
                {
                    //not enough data;something wrong
                    return false;
                }
            }
            else
            {
                buffLen = m_memPoolSize - m_pRear;
                if (buffLen >= len)
                {
                    Buffer.BlockCopy(m_pMemPool, (int)m_pRear, pBuffer, 0, (int)len);
                }
                else
                {
                    Buffer.BlockCopy(m_pMemPool, (int)m_pRear, pBuffer, 0, (int)buffLen);
                    Buffer.BlockCopy(m_pMemPool, 0, pBuffer, (int)buffLen, (int)(len - buffLen));
                }
            }
            return true;
        }

        public uint GetDataLen()
        {
            if ((m_pHeader >= m_pRear))
            {
                return (m_pHeader - m_pRear);

            }
            return (m_memPoolSize - (m_pRear - m_pHeader));
        }

        public uint GetFreeLen()
        {
            if (m_pHeader < m_pRear)
            {
                //to make sure that m_pHeader would never catch up with m_pUsed;
                return ((m_pRear - m_pHeader) - 1);
            }
            return ((m_memPoolSize - (m_pHeader - m_pRear)) - 1);
        }
    }
}